<?php
/**
 * MySQLi
 * 
 * @category	DataSource
 * @package		dayscript.datasource.sql.adapter
 * @subpackage	
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license		
 * @version		1.0
 * @version		$Revision: 0 $
 * @filesource	
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-26 $
 */

namespace dayscript\datasource\sql\adapter;

/**
 * MySQLi
 * 
 * @category	DataSource
 * @package		dayscript.datasource.sql.adapter
 * @subpackage	
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license		
 * @version		1.0
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		\dayscript\datasource\sql\Database
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-31 $
 */
class MySQLi extends \dayscript\datasource\sql\Database	{
	
	/**
	 * MySQLi symbol for quoting identifiers
	 * @var char $quoteIdentifierSymbol
	 */
	protected $quoteIdentifierSymbol = '`'; 
	
	/**
	 * @see \dayscript\datasource\sql\Database
	 * @var array $numericDataTypes
	 */
	protected $numericDataTypes = array (
		self::TYPE_INT		=> self::TYPE_INT,
		self::TYPE_BIGINT	=> self::TYPE_BIGINT,
		self::TYPE_FLOAT	=> self::TYPE_FLOAT,
		'INT'				=> self::TYPE_INT,
		'INTEGER'			=> self::TYPE_INT,
		'MEDIUMINT'			=> self::TYPE_INT,
		'SMALLINT'			=> self::TYPE_INT,
		'TINYINT'			=> self::TYPE_INT,
		'BIGINT'			=> self::TYPE_BIGINT,
		'SERIAL'			=> self::TYPE_BIGINT,
		'DEC'				=> self::TYPE_FLOAT,
		'DECIMAL'			=> self::TYPE_FLOAT,
		'DOUBLE'			=> self::TYPE_FLOAT,
		'DOUBLE PRECISION'	=> self::TYPE_FLOAT,
		'FIXED'				=> self::TYPE_FLOAT,
		'FLOAT'				=> self::TYPE_FLOAT
	);
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	protected function connect( )	{
		$this->connection = \mysqli_init( );

		if( \is_array( $this->config['options'] ) )	{
			foreach( $this->config['options'] as $option => $value )	{
				if( \is_string( $option ) )	{
					$option = \constant( \strtoupper( $option ) );
					if( $option === null )
						continue;
				}
				\mysqli_options( $this->connection, $option, $value );
			}
		}
		
		$connected = \mysqli_real_connect ( 
			$this->connection,
			$this->config['host'],
			$this->config['username'],
			$this->config['password'],
			$this->config['database'],
			$this->config['port'] 
		);
		
		if ( $connected === false || \mysqli_connect_errno( ) )	{
			$this->closeConnection( );
			throw new \dayscript\exception\Exception( \mysqli_connect_error( ) );
		}

		if ( $this->config['charset'] )
			\mysqli_set_charset( $this->connection, $this->config['charset'] );
		
		return ( $this->connection instanceof \mysqli );
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	protected function disconnect( )	{
		if ( $this->connected )
			$this->connection->close( );
		
		$this->connection = null;
		return true;
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	public function destroy( )	{
		$this->disconnect( );
		$this->config = null;
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 * TODO
	 */
	public function init( $name, \dayscript\Map $conf = null )	{
		
		if( $this->config )
			throw new \dayscript\exception\Exception( 'Trying to open an already initialized database connection.' );
		
		if ( !\extension_loaded( 'mysqli' ) )
			throw new \dayscript\exception\Exception( 'The MySQLi extension is required for this adapter but the extension is not loaded.' );

		if( !$name )
			throw new \dayscript\exception\Exception( 'Undefined connection identifier.' );
		
		$this->name = $name;
		
		$this->config = new \dayscript\Map( $conf );
		
		if( !$this->config['host'] )
			$this->config['host'] = 'localhost';
		if( !$this->config['port'] || !\is_long( $this->config['port'] ) )
			$this->config['port'] = 3306;
		if( !$this->config['username'] )	{
			$this->config = null;
			throw new \dayscript\exception\Exception( 'Undefined connection user name.' );
		}
		
	}
	
	/*
	 * Transactions
	 */
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	protected function transactionBegin( )	{
		$this->getConnection( );
		$this->connection->autocommit( false );
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	protected function transactionCommit( )	{
		$this->getConnection( );
		$this->connection->commit( );
		$this->connection->autocommit( true );
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	protected function transactionRollBack( )	{
		$this->getConnection( );
		$this->connection->rollback( );
		$this->connection->autocommit( true );
	}

	/*
	 * Cache
	 */
	/**
	 * @see \dayscript\datasource\sql\Database
	 * TODO
	 */
	public function setCacheEnable( $enabled )	{
		echo "setCacheEnable";
		$this->cacheable = $enabled;
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 * TODO
	 */
	public function initCache( )	{
		echo "initCache";
		return false;
	}
	
	/*
	 * General
	 */
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	public function getQuoteIdentifierSymbol( )	{
		return '`';
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	public function listTables( )	{
		$result = array( );
		$sql = 'SHOW TABLES';
		$queryResult = $this->getConnection( )->query( $sql );
		if ( $queryResult )	{
			while ( ( $row = $queryResult->fetch_row( ) ) != null )
				$result[] = $row[0];
			$queryResult->close( );
		}
		else
			throw new \dayscript\exception\Exception( $this->getConnection( )->error );
		return $result;
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	public function describeTable( $tableName, $schemaName = null )	{
		if ( $schemaName )
			$sql = 'DESCRIBE ' . $this->quoteIdentifier( $schemaName . '.' . $tableName, true );
		else
			$sql = 'DESCRIBE ' . $this->quoteIdentifier( $tableName, true );

		$queryResult = $this->getConnection( )->query( $sql );
		if ( $queryResult )	{
			while ( ( $row = $queryResult->fetch_assoc( ) ) != null )
				$result[] = $row;
			$queryResult->close( );
		}
		else
			throw new \dayscript\exception\Exception( $this->getConnection( )->error );
		
		$desc = array( );

		$row_defaults = array(
            'Length'          => null,
            'Scale'           => null,
            'Precision'       => null,
            'Unsigned'        => null,
            'Primary'         => false,
            'PrimaryPosition' => null,
            'Identity'        => false
		);
		$i = 1;
		$p = 1;
		foreach ( $result as $key => $row )	{
			$row = \array_merge( $row_defaults, $row );
			if ( \preg_match( '/unsigned/', $row['Type'] ) )
				$row['Unsigned'] = true;
			
			if ( \preg_match( '/^((?:var)?char)\((\d+)\)/', $row['Type'], $matches ) )	{
				$row['Type'] = $matches[1];
				$row['Length'] = $matches[2];
			}
			else if ( \preg_match( '/^decimal\((\d+),(\d+)\)/', $row['Type'], $matches ) )	{
				$row['Type'] = 'decimal';
				$row['Precision'] = $matches[1];
				$row['Scale'] = $matches[2];
			}
			else if ( \preg_match( '/^float\((\d+),(\d+)\)/', $row['Type'], $matches ) )	{
				$row['Type'] = 'float';
				$row['Precision'] = $matches[1];
				$row['Scale'] = $matches[2];
			}
			else if ( \preg_match( '/^((?:big|medium|small|tiny)?int)\((\d+)\)/', $row['Type'], $matches ) )
				$row['Type'] = $matches[1];
			
			if ( \strtoupper( $row['Key'] ) == 'PRI' )	{
				$row['Primary'] = true;
				$row['PrimaryPosition'] = $p;
				$row['Identity'] = ( $row['Extra'] == 'auto_increment' );
				++$p;
			}
			$desc[$row['Field']] = array (
                'SCHEMA_NAME'      => null,
                'TABLE_NAME'       => $tableName,
                'COLUMN_NAME'      => $row['Field'],
                'COLUMN_POSITION'  => $i,
                'DATA_TYPE'        => $row['Type'],
                'DEFAULT'          => $row['Default'],
                'NULLABLE'         => ( $row['Null'] == 'YES' ),
                'LENGTH'           => $row['Length'],
                'SCALE'            => $row['Scale'],
                'PRECISION'        => $row['Precision'],
                'UNSIGNED'         => $row['Unsigned'],
                'PRIMARY'          => $row['Primary'],
                'PRIMARY_POSITION' => $row['PrimaryPosition'],
                'IDENTITY'         => $row['Identity']
			);
			++$i;
		}
		return $desc;
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	public function prepare( $sql )	{
		$this->getConnection( );
		if ( $this->statement )
			$this->statement->close( );
		
		$this->statement = new \dayscript\datasource\sql\statement\MySQLi( $this, $sql );
		$this->statement->setFetchMode( $this->fetchMode );
		return $this->statement;
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	public function lastInsertId( $tableName = null, $primaryKey = null )	{
		return $this->connection->insert_id;
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	public function setFetchMode( $mode )	{
		switch ( $mode )	{
			case self::FETCH_LAZY:
			case self::FETCH_ASSOC:
			case self::FETCH_NUM:
			case self::FETCH_BOTH:
			case self::FETCH_NAMED:
			case self::FETCH_OBJ:
				$this->fetchMode = $mode;
				break;
			case self::FETCH_BOUND:	// bound to PHP variable
				throw new \dayscript\exception\Exception( 'FETCH_BOUND is not supported yet.' );
				break;
			default:
				throw new \dayscript\exception\Exception( 'Invalid fetch mode "' . $mode . '" specified.' );
		}
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	public function limit( $sql, $count, $offset = 0 )	{
		if ( !\is_int( $count ) || $count <= 0 )
			throw new \InvalidArgumentException( 'LIMIT argument count must be an integer greater than zero but "' . $count . '" passed.' );

		if ( !\is_int( $count ) || $count <= 0 )
			throw new \InvalidArgumentException( 'LIMIT argument offset must be an integer greater than or equals to zero but "' . $offset . '" passed.' );
		
		$sql .= ' LIMIT ' . $count;
		if ( $offset > 0 )
			$sql .= ' OFFSET ' . $offset;

		return $sql;
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	public function supportsParameters( $type )	{
		switch ( $type )	{
			case 'positional':
				return true;
			case 'named':
			default:
				return false;
		}
		return false;
	}
	/**
	 * @see \dayscript\datasource\sql\Database
	 */
	public function getServerVersion( )	{
		$this->getConnection( );
		$version = $this->connection->server_version;
		$major = (int)( $version / 10000 );
		$minor = (int)( $version % 10000 / 100 );
		$revision = (int) ( $version % 100 );
		return $major . '.' . $minor . '.' . $revision;
	}

}
